/*-------------------------------------------------------------------------
 *
 * fe-print.c
 *	  用于美化打印查询结果的函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * 这些函数以前是 fe-exec.c 的一部分，但实际上并不属于那里。
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-print.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include <signal.h>

#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <sys/ioctl.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#else
#ifndef WIN32
#include <sys/termios.h>
#endif
#endif

#include "libpq-fe.h"
#include "libpq-int.h"


static bool fc_do_field(const PQprintOpt *fc_po, const PGresult *fc_res,
					 const int fc_i, const int fc_j, const int fc_fs_len,
					 char **fc_fields,
					 const int fc_nFields, const char **fc_fieldNames,
					 unsigned char *fc_fieldNotNum, int *fc_fieldMax,
					 const int fc_fieldMaxLen, FILE *fc_fout);
static char *fc_do_header(FILE *fc_fout, const PQprintOpt *fc_po, const int fc_nFields,
					   int *fc_fieldMax, const char **fc_fieldNames, unsigned char *fc_fieldNotNum,
					   const int fc_fs_len, const PGresult *fc_res);
static void fc_output_row(FILE *fc_fout, const PQprintOpt *fc_po, const int fc_nFields, char **fc_fields,
					   unsigned char *fc_fieldNotNum, int *fc_fieldMax, char *fc_border,
					   const int fc_row_index);
static void fc_fill(int fc_length, int fc_max, char fc_filler, FILE *fc_fp);

/*
 * PQprint()
 *
 * 格式化查询结果以便打印。
 *
 * PQprintOpt 是一个 typedef（结构体），包含各种标志和选项。有关详细信息，请查阅 libpq-fe.h。
 *
 * 这个函数可能会在将来被移除，因为 psql 不再使用它。然而，目前尚不清楚外部客户端在多大程度上使用它。
 */
void PQprint(FILE *fc_fout, const PGresult *fc_res, const PQprintOpt *fc_po)
{
	int			fc_nFields;

	fc_nFields = PQnfields(fc_res);

	if (fc_nFields > 0)
	{							/* 只打印至少有 1 个字段的行。 */
		int			fc_i,
					fc_j;
		int			fc_nTups;
		int		   *fc_fieldMax = NULL;	/* 以防我们不使用它们 */
		unsigned char *fc_fieldNotNum = NULL;
		char	   *fc_border = NULL;
		char	  **fc_fields = NULL;
		const char **fc_fieldNames = NULL;
		int			fc_fieldMaxLen = 0;
		int			fc_numFieldName;
		int			fc_fs_len = strlen(fc_po->fieldSep);
		int			fc_total_line_length = 0;
		bool		fc_usePipe = false;
		char	   *fc_pagerenv;

#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
		sigset_t	fc_osigset;
		bool		fc_sigpipe_masked = false;
		bool		fc_sigpipe_pending;
#endif
#if !defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
		pqsigfunc	fc_oldsigpipehandler = NULL;
#endif

#ifdef TIOCGWINSZ
		struct winsize fc_screen_size;
#else
		struct winsize
		{
			int			ws_row;
			int			ws_col;
		}			fc_screen_size;
#endif

		fc_nTups = PQntuples(fc_res);
		fc_fieldNames = (const char **) calloc(fc_nFields, sizeof(char *));
		fc_fieldNotNum = (unsigned char *) calloc(fc_nFields, 1);
		fc_fieldMax = (int *) calloc(fc_nFields, sizeof(int));
		if (!fc_fieldNames || !fc_fieldNotNum || !fc_fieldMax)
		{
			fprintf(stderr, libpq_gettext("out of memory\n"));
			goto exit;
		}
		for (fc_numFieldName = 0;
			 fc_po->fieldName && fc_po->fieldName[fc_numFieldName];
			 fc_numFieldName++)
			;
		for (fc_j = 0; fc_j < fc_nFields; fc_j++)
		{
			int			fc_len;
			const char *fc_s = (fc_j < fc_numFieldName && fc_po->fieldName[fc_j][0]) ?
			fc_po->fieldName[fc_j] : PQfname(fc_res, fc_j);

			fc_fieldNames[fc_j] = fc_s;
			fc_len = fc_s ? strlen(fc_s) : 0;
			fc_fieldMax[fc_j] = fc_len;
			fc_len += fc_fs_len;
			if (fc_len > fc_fieldMaxLen)
				fc_fieldMaxLen = fc_len;
			fc_total_line_length += fc_len;
		}

		fc_total_line_length += fc_nFields * strlen(fc_po->fieldSep) + 1;

		if (fc_fout == NULL)
			fc_fout = stdout;
		if (fc_po->pager && fc_fout == stdout && isatty(fileno(stdin)) &&
			isatty(fileno(stdout)))
		{
			/*
			 * 如果我们认为输出将超过一个屏幕，尝试将其
			 * 管道到 pager 程序。
			 */
#ifdef TIOCGWINSZ
			if (ioctl(fileno(stdout), TIOCGWINSZ, &fc_screen_size) == -1 ||
				fc_screen_size.ws_col == 0 ||
				fc_screen_size.ws_row == 0)
			{
				fc_screen_size.ws_row = 24;
				fc_screen_size.ws_col = 80;
			}
#else
			fc_screen_size.ws_row = 24;
			fc_screen_size.ws_col = 80;
#endif

			/*
			 * 由于这个函数不再被 psql 使用，我们不检查
			 * PSQL_PAGER。假设的外部用户可能希望这样做，但为了向后兼容，我们将坚持只检查 PAGER。
			 */
			fc_pagerenv = getenv("PAGER");
			/* 如果 PAGER 没有设置、为空或全是空白，不使用 pager */
			if (fc_pagerenv != NULL &&
				strspn(fc_pagerenv, " \t\r\n") != strlen(fc_pagerenv) &&
				!fc_po->html3 &&
				((fc_po->expanded &&
				  fc_nTups * (fc_nFields + 1) >= fc_screen_size.ws_row) ||
				 (!fc_po->expanded &&
				  fc_nTups * (fc_total_line_length / fc_screen_size.ws_col + 1) *
				  (1 + (fc_po->standard != 0)) >= fc_screen_size.ws_row -
				  (fc_po->header != 0) *
				  (fc_total_line_length / fc_screen_size.ws_col + 1) * 2
				  - (fc_po->header != 0) * 2	/* 行计数和换行 */
				  )))
			{
				fc_fout = popen(fc_pagerenv, "w");
				if (fc_fout)
				{
					fc_usePipe = true;
#ifndef WIN32
#ifdef ENABLE_THREAD_SAFETY
					if (pq_block_sigpipe(&fc_osigset, &fc_sigpipe_pending) == 0)
						fc_sigpipe_masked = true;
#else
					fc_oldsigpipehandler = pqsignal(SIGPIPE, SIG_IGN);
#endif							/* 启用线程安全 */
#endif							/* WIN32 */
				}
				else
					fc_fout = stdout;
			}
		}

		if (!fc_po->expanded && (fc_po->align || fc_po->html3))
		{
			fc_fields = (char **) calloc((size_t) fc_nTups + 1,
									  fc_nFields * sizeof(char *));
			if (!fc_fields)
			{
				fprintf(stderr, libpq_gettext("out of memory\n"));
				goto exit;
			}
		}
		else if (fc_po->header && !fc_po->html3)
		{
			if (fc_po->expanded)
			{
				if (fc_po->align)
					fprintf(fc_fout, libpq_gettext("%-*s%s Value\n"),
							fc_fieldMaxLen - fc_fs_len, libpq_gettext("Field"), fc_po->fieldSep);
				else
					fprintf(fc_fout, libpq_gettext("%s%sValue\n"), libpq_gettext("Field"), fc_po->fieldSep);
			}
			else
			{
				int			fc_len = 0;

				for (fc_j = 0; fc_j < fc_nFields; fc_j++)
				{
					const char *fc_s = fc_fieldNames[fc_j];

					fputs(fc_s, fc_fout);
					fc_len += strlen(fc_s) + fc_fs_len;
					if ((fc_j + 1) < fc_nFields)
						fputs(fc_po->fieldSep, fc_fout);
				}
				fputc('\n', fc_fout);
				for (fc_len -= fc_fs_len; fc_len--; fputc('-', fc_fout));
				fputc('\n', fc_fout);
			}
		}
		if (fc_po->expanded && fc_po->html3)
		{
			if (fc_po->caption)
				fprintf(fc_fout, "<center><h2>%s</h2></center>\n", fc_po->caption);
			else
				fprintf(fc_fout,
						"<center><h2>"
						"Query retrieved %d rows * %d fields"
						"</h2></center>\n",
						fc_nTups, fc_nFields);
		}
		for (fc_i = 0; fc_i < fc_nTups; fc_i++)
		{
			if (fc_po->expanded)
			{
				if (fc_po->html3)
					fprintf(fc_fout,
							"<table %s><caption align=\"top\">%d</caption>\n",
							fc_po->tableOpt ? fc_po->tableOpt : "", fc_i);
				else
					fprintf(fc_fout, libpq_gettext("-- RECORD %d --\n"), fc_i);
			}
			for (fc_j = 0; fc_j < fc_nFields; fc_j++)
			{
				if (!fc_do_field(fc_po, fc_res, fc_i, fc_j, fc_fs_len, fc_fields, fc_nFields,
							  fc_fieldNames, fc_fieldNotNum,
							  fc_fieldMax, fc_fieldMaxLen, fc_fout))
					goto exit;
			}
			if (fc_po->html3 && fc_po->expanded)
				fputs("</table>\n", fc_fout);
		}
		if (!fc_po->expanded && (fc_po->align || fc_po->html3))
		{
			if (fc_po->html3)
			{
				if (fc_po->header)
				{
					if (fc_po->caption)
						fprintf(fc_fout,
								"<table %s><caption align=\"top\">%s</caption>\n",
								fc_po->tableOpt ? fc_po->tableOpt : "",
								fc_po->caption);
					else
						fprintf(fc_fout,
								"<table %s><caption align=\"top\">"
								"Retrieved %d rows * %d fields"
								"</caption>\n",
								fc_po->tableOpt ? fc_po->tableOpt : "", fc_nTups, fc_nFields);
				}
				else
					fprintf(fc_fout, "<table %s>", fc_po->tableOpt ? fc_po->tableOpt : "");
			}
			if (fc_po->header)
				fc_border = fc_do_header(fc_fout, fc_po, fc_nFields, fc_fieldMax, fc_fieldNames,
								   fc_fieldNotNum, fc_fs_len, fc_res);
			for (fc_i = 0; fc_i < fc_nTups; fc_i++)
				fc_output_row(fc_fout, fc_po, fc_nFields, fc_fields,
						   fc_fieldNotNum, fc_fieldMax, fc_border, fc_i);
		}
		if (fc_po->header && !fc_po->html3)
			fprintf(fc_fout, "(%d row%s)\n\n", PQntuples(fc_res),
					(PQntuples(fc_res) == 1) ? "" : "s");
		if (fc_po->html3 && !fc_po->expanded)
			fputs("</table>\n", fc_fout);

exit:
		if (fc_fieldMax)
			free(fc_fieldMax);
		if (fc_fieldNotNum)
			free(fc_fieldNotNum);
		if (fc_border)
			free(fc_border);
		if (fc_fields)
		{
			/* 如果 calloc 成功，这不应该溢出 size_t */
			size_t		fc_numfields = ((size_t) fc_nTups + 1) * (size_t) fc_nFields;

			while (fc_numfields-- > 0)
			{
				if (fc_fields[fc_numfields])
					free(fc_fields[fc_numfields]);
			}
			free(fc_fields);
		}
		if (fc_fieldNames)
			free((void *) fc_fieldNames);
		if (fc_usePipe)
		{
#ifdef WIN32
			_pclose(fout);
#else
			pclose(fc_fout);

#ifdef ENABLE_THREAD_SAFETY
			/* 我们无法轻易验证是否发生了 EPIPE，因此假设发生了 */
			if (fc_sigpipe_masked)
				pq_reset_sigpipe(&fc_osigset, fc_sigpipe_pending, true);
#else
			pqsignal(SIGPIPE, fc_oldsigpipehandler);
#endif							/* 启用线程安全 */
#endif							/* WIN32 */
		}
	}
}


static bool fc_do_field(const PQprintOpt *fc_po, const PGresult *fc_res,
		 const int fc_i, const int fc_j, const int fc_fs_len,
		 char **fc_fields,
		 const int fc_nFields, char const **fc_fieldNames,
		 unsigned char *fc_fieldNotNum, int *fc_fieldMax,
		 const int fc_fieldMaxLen, FILE *fc_fout)
{
	const char *fc_pval,
			   *fc_p;
	int			fc_plen;
	bool		fc_skipit;

	fc_plen = PQgetlength(fc_res, fc_i, fc_j);
	fc_pval = PQgetvalue(fc_res, fc_i, fc_j);

	if (fc_plen < 1 || !fc_pval || !*fc_pval)
	{
		if (fc_po->align || fc_po->expanded)
			fc_skipit = true;
		else
		{
			fc_skipit = false;
			goto efield;
		}
	}
	else
		fc_skipit = false;

	if (!fc_skipit)
	{
		if (fc_po->align && !fc_fieldNotNum[fc_j])
		{
			/* 检测字段是否包含非数字数据 */
			char		fc_ch = '0';

			for (fc_p = fc_pval; *fc_p; fc_p += PQmblenBounded(fc_p, fc_res->client_encoding))
			{
				fc_ch = *fc_p;
				if (!((fc_ch >= '0' && fc_ch <= '9') ||
					  fc_ch == '.' ||
					  fc_ch == 'E' ||
					  fc_ch == 'e' ||
					  fc_ch == ' ' ||
					  fc_ch == '-'))
				{
					fc_fieldNotNum[fc_j] = 1;
					break;
				}
			}

			/*
			 * 上面的循环将认为第一列的 E 是数字；此外，我们
			 * 坚持在最后一列中有一个数字以表示为数字。这个测试
			 * 仍然不是万无一失的，但它处理了大部分情况。
			 */
			if (*fc_pval == 'E' || *fc_pval == 'e' ||
				!(fc_ch >= '0' && fc_ch <= '9'))
				fc_fieldNotNum[fc_j] = 1;
		}

		if (!fc_po->expanded && (fc_po->align || fc_po->html3))
		{
			if (fc_plen > fc_fieldMax[fc_j])
				fc_fieldMax[fc_j] = fc_plen;
			if (!(fc_fields[fc_i * fc_nFields + fc_j] = (char *) malloc(fc_plen + 1)))
			{
				fprintf(stderr, libpq_gettext("out of memory\n"));
				return false;
			}
			strcpy(fc_fields[fc_i * fc_nFields + fc_j], fc_pval);
		}
		else
		{
			if (fc_po->expanded)
			{
				if (fc_po->html3)
					fprintf(fc_fout,
							"<tr><td align=\"left\"><b>%s</b></td>"
							"<td align=\"%s\">%s</td></tr>\n",
							fc_fieldNames[fc_j],
							fc_fieldNotNum[fc_j] ? "left" : "right",
							fc_pval);
				else
				{
					if (fc_po->align)
						fprintf(fc_fout,
								"%-*s%s %s\n",
								fc_fieldMaxLen - fc_fs_len, fc_fieldNames[fc_j],
								fc_po->fieldSep,
								fc_pval);
					else
						fprintf(fc_fout,
								"%s%s%s\n",
								fc_fieldNames[fc_j], fc_po->fieldSep, fc_pval);
				}
			}
			else
			{
				if (!fc_po->html3)
				{
					fputs(fc_pval, fc_fout);
			efield:
					if ((fc_j + 1) < fc_nFields)
						fputs(fc_po->fieldSep, fc_fout);
					else
						fputc('\n', fc_fout);
				}
			}
		}
	}
	return true;
}


static char * fc_do_header(FILE *fc_fout, const PQprintOpt *fc_po, const int fc_nFields, int *fc_fieldMax,
		  const char **fc_fieldNames, unsigned char *fc_fieldNotNum,
		  const int fc_fs_len, const PGresult *fc_res)
{
	int			fc_j;				/* 循环索引 */
	char	   *fc_border = NULL;

	if (fc_po->html3)
		fputs("<tr>", fc_fout);
	else
	{
		int			fc_tot = 0;
		int			fc_n = 0;
		char	   *fc_p = NULL;

		for (; fc_n < fc_nFields; fc_n++)
			fc_tot += fc_fieldMax[fc_n] + fc_fs_len + (fc_po->standard ? 2 : 0);
		if (fc_po->standard)
			fc_tot += fc_fs_len * 2 + 2;
		fc_border = malloc(fc_tot + 1);
		if (!fc_border)
		{
			fprintf(stderr, libpq_gettext("out of memory\n"));
			return NULL;
		}
		fc_p = fc_border;
		if (fc_po->standard)
		{
			char	   *fc_fs = fc_po->fieldSep;

			while (*fc_fs++)
				*fc_p++ = '+';
		}
		for (fc_j = 0; fc_j < fc_nFields; fc_j++)
		{
			int			fc_len;

			for (fc_len = fc_fieldMax[fc_j] + (fc_po->standard ? 2 : 0); fc_len--; *fc_p++ = '-');
			if (fc_po->standard || (fc_j + 1) < fc_nFields)
			{
				char	   *fc_fs = fc_po->fieldSep;

				while (*fc_fs++)
					*fc_p++ = '+';
			}
		}
		*fc_p = '\0';
		if (fc_po->standard)
			fprintf(fc_fout, "%s\n", fc_border);
	}
	if (fc_po->standard)
		fputs(fc_po->fieldSep, fc_fout);
	for (fc_j = 0; fc_j < fc_nFields; fc_j++)
	{
		const char *fc_s = PQfname(fc_res, fc_j);

		if (fc_po->html3)
		{
			fprintf(fc_fout, "<th align=\"%s\">%s</th>",
					fc_fieldNotNum[fc_j] ? "left" : "right", fc_fieldNames[fc_j]);
		}
		else
		{
			int			fc_n = strlen(fc_s);

			if (fc_n > fc_fieldMax[fc_j])
				fc_fieldMax[fc_j] = fc_n;
			if (fc_po->standard)
				fprintf(fc_fout,
						fc_fieldNotNum[fc_j] ? " %-*s " : " %*s ",
						fc_fieldMax[fc_j], fc_s);
			else
				fprintf(fc_fout, fc_fieldNotNum[fc_j] ? "%-*s" : "%*s", fc_fieldMax[fc_j], fc_s);
			if (fc_po->standard || (fc_j + 1) < fc_nFields)
				fputs(fc_po->fieldSep, fc_fout);
		}
	}
	if (fc_po->html3)
		fputs("</tr>\n", fc_fout);
	else
		fprintf(fc_fout, "\n%s\n", fc_border);
	return fc_border;
}


static void fc_output_row(FILE *fc_fout, const PQprintOpt *fc_po, const int fc_nFields, char **fc_fields,
		   unsigned char *fc_fieldNotNum, int *fc_fieldMax, char *fc_border,
		   const int fc_row_index)
{
	int			fc_field_index;	/* 循环索引 */

	if (fc_po->html3)
		fputs("<tr>", fc_fout);
	else if (fc_po->standard)
		fputs(fc_po->fieldSep, fc_fout);
	for (fc_field_index = 0; fc_field_index < fc_nFields; fc_field_index++)
	{
		char	   *fc_p = fc_fields[fc_row_index * fc_nFields + fc_field_index];

		if (fc_po->html3)
			fprintf(fc_fout, "<td align=\"%s\">%s</td>",
					fc_fieldNotNum[fc_field_index] ? "left" : "right", fc_p ? fc_p : "");
		else
		{
			fprintf(fc_fout,
					fc_fieldNotNum[fc_field_index] ?
					(fc_po->standard ? " %-*s " : "%-*s") :
					(fc_po->standard ? " %*s " : "%*s"),
					fc_fieldMax[fc_field_index],
					fc_p ? fc_p : "");
			if (fc_po->standard || fc_field_index + 1 < fc_nFields)
				fputs(fc_po->fieldSep, fc_fout);
		}
	}
	if (fc_po->html3)
		fputs("</tr>", fc_fout);
	else if (fc_po->standard)
		fprintf(fc_fout, "\n%s", fc_border);
	fputc('\n', fc_fout);
}



/*
 * 真正古老的打印例程
 */

void PQdisplayTuples(const PGresult *fc_res,
				FILE *fc_fp,		/* 输出发送的位置 */
				int fc_fillAlign,	/* 用空格填充字段 */
				const char *fc_fieldSep,	/* 字段分隔符 */
				int fc_printHeader,	/* 显示标题？ */
				int fc_quiet
)
{
#define DEFAULT_FIELD_SEP " "

	int			fc_i,
				fc_j;
	int			fc_nFields;
	int			fc_nTuples;
	int		   *fc_fLength = NULL;

	if (fc_fieldSep == NULL)
		fc_fieldSep = DEFAULT_FIELD_SEP;

	/* 获取一些关于结果的有用信息 */
	fc_nFields = PQnfields(fc_res);
	fc_nTuples = PQntuples(fc_res);

	if (fc_fp == NULL)
		fc_fp = stdout;

	/* 计算字段长度以对齐 */
	/* 对于非常大的结果，将会稍微耗时 */
	if (fc_fillAlign)
	{
		fc_fLength = (int *) malloc(fc_nFields * sizeof(int));
		if (!fc_fLength)
		{
			fprintf(stderr, libpq_gettext("out of memory\n"));
			return;
		}

		for (fc_j = 0; fc_j < fc_nFields; fc_j++)
		{
			fc_fLength[fc_j] = strlen(PQfname(fc_res, fc_j));
			for (fc_i = 0; fc_i < fc_nTuples; fc_i++)
			{
				int			fc_flen = PQgetlength(fc_res, fc_i, fc_j);

				if (fc_flen > fc_fLength[fc_j])
					fc_fLength[fc_j] = fc_flen;
			}
		}
	}

	if (fc_printHeader)
	{
		/* 首先，打印出属性名称 */
		for (fc_i = 0; fc_i < fc_nFields; fc_i++)
		{
			fputs(PQfname(fc_res, fc_i), fc_fp);
			if (fc_fillAlign)
				fc_fill(strlen(PQfname(fc_res, fc_i)), fc_fLength[fc_i], ' ', fc_fp);
			fputs(fc_fieldSep, fc_fp);
		}
		fprintf(fc_fp, "\n");

		/* 为属性名称加下划线 */
		for (fc_i = 0; fc_i < fc_nFields; fc_i++)
		{
			if (fc_fillAlign)
				fc_fill(0, fc_fLength[fc_i], '-', fc_fp);
			fputs(fc_fieldSep, fc_fp);
		}
		fprintf(fc_fp, "\n");
	}

	/* 其次，打印出实例 */
	for (fc_i = 0; fc_i < fc_nTuples; fc_i++)
	{
		for (fc_j = 0; fc_j < fc_nFields; fc_j++)
		{
			fprintf(fc_fp, "%s", PQgetvalue(fc_res, fc_i, fc_j));
			if (fc_fillAlign)
				fc_fill(strlen(PQgetvalue(fc_res, fc_i, fc_j)), fc_fLength[fc_j], ' ', fc_fp);
			fputs(fc_fieldSep, fc_fp);
		}
		fprintf(fc_fp, "\n");
	}

	if (!fc_quiet)
		fprintf(fc_fp, "\nQuery returned %d row%s.\n", PQntuples(fc_res),
				(PQntuples(fc_res) == 1) ? "" : "s");

	fflush(fc_fp);

	if (fc_fLength)
		free(fc_fLength);
}



void PQprintTuples(const PGresult *fc_res,
			  FILE *fc_fout,		/* 输出流 */
			  int fc_PrintAttNames,	/* 是否打印属性名称 */
			  int fc_TerseOutput,	/* 是否有分隔条？ */
			  int fc_colWidth		/* 列的宽度，如果为 0，则使用可变宽度 */
)
{
	int			fc_nFields;
	int			fc_nTups;
	int			fc_i,
				fc_j;
	char		fc_formatString[80];
	char	   *fc_tborder = NULL;

	fc_nFields = PQnfields(fc_res);
	fc_nTups = PQntuples(fc_res);

	if (fc_colWidth > 0)
		sprintf(fc_formatString, "%%s %%-%ds", fc_colWidth);
	else
		sprintf(fc_formatString, "%%s %%s");

	if (fc_nFields > 0)
	{							/* 只打印至少有 1 个字段的行。 */

		if (!fc_TerseOutput)
		{
			int			fc_width;

			fc_width = fc_nFields * 14;
			fc_tborder = (char *) malloc(fc_width + 1);
			if (!fc_tborder)
			{
				fprintf(stderr, libpq_gettext("out of memory\n"));
				return;
			}
			for (fc_i = 0; fc_i < fc_width; fc_i++)
				fc_tborder[fc_i] = '-';
			fc_tborder[fc_width] = '\0';
			fprintf(fc_fout, "%s\n", fc_tborder);
		}

		for (fc_i = 0; fc_i < fc_nFields; fc_i++)
		{
			if (fc_PrintAttNames)
			{
				fprintf(fc_fout, fc_formatString,
						fc_TerseOutput ? "" : "|",
						PQfname(fc_res, fc_i));
			}
		}

		if (fc_PrintAttNames)
		{
			if (fc_TerseOutput)
				fprintf(fc_fout, "\n");
			else
				fprintf(fc_fout, "|\n%s\n", fc_tborder);
		}

		for (fc_i = 0; fc_i < fc_nTups; fc_i++)
		{
			for (fc_j = 0; fc_j < fc_nFields; fc_j++)
			{
				const char *fc_pval = PQgetvalue(fc_res, fc_i, fc_j);

				fprintf(fc_fout, fc_formatString,
						fc_TerseOutput ? "" : "|",
						fc_pval ? fc_pval : "");
			}
			if (fc_TerseOutput)
				fprintf(fc_fout, "\n");
			else
				fprintf(fc_fout, "|\n%s\n", fc_tborder);
		}
	}

	if (fc_tborder)
		free(fc_tborder);
}


/* 仅将最大长度的填充字符发送到 fp */

static void fc_fill(int fc_length, int fc_max, char fc_filler, FILE *fc_fp)
{
	int			fc_count;

	fc_count = fc_max - fc_length;
	while (fc_count-- >= 0)
		putc(fc_filler, fc_fp);
}
